package com.surprise.biz.service;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import redis.clients.jedis.Jedis;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.surprise.base.bo.Triple;
import com.surprise.base.util.FlatUtils;
import com.surprise.base.util.ListUtils;
import com.surprise.base.util.MapUtils;
import com.surprise.biz.bo.BatchChangeItem;
import com.surprise.biz.bo.SNSBO;
import com.surprise.biz.bo.serializer.MessageSerializer;
import com.surprise.db.dao.StoreDAO;
import com.surprise.db.po.Store;
import com.surprise.redis.RedisKeyGenerator;
import com.surprise.redis.util.RedisUtils;

/**
 * StoreService.java
 * @author nick
 * 2015年2月13日
 */
@Service
public class StoreService {
    @Autowired
    @Qualifier("jsonMessageSerializer")
    private MessageSerializer messageSerializer;

    @Autowired
    private StoreDAO storeDAO;
    
    /**
     * 单个收藏
     * 
     * @param jedis
     * @param bo
     * @return
     */
    public int add(Jedis jedis, SNSBO bo) {
        int type = bo.getType();
        long uid = bo.getUid();
        List<Long> iids = Lists.newArrayList(bo.getIid());
        if (!exists(jedis, type, uid, bo.getIid())) { // 如果是收藏(不存在在已有队列，则收藏)
            // 把iid增加到用户收藏队列中
            addUserMap(jedis, type, uid, iids);
            // 增加pid记录收藏计数
            addCounter(jedis, type, iids);
            // 加到消息队列
            addToMQ(jedis, uid, iids, type, 1);
        }
        return 1;
    }

    /**
     * 取消单个收藏
     * 
     * @param jedis
     * @param bo
     * @return
     */
    public int del(Jedis jedis, SNSBO bo) {
        int type = bo.getType();
        long uid = bo.getUid();
        List<Long> iids = Lists.newArrayList(bo.getIid());
        if (exists(jedis, type, uid, bo.getIid())) { // 如果是取消收藏(存在于已有队列，则取消收藏)
            // 把pid从用户收藏队列去除
            delUserMap(jedis, type, uid, iids);
            // 减少pid记录收藏计数
            delCounter(jedis, type, iids);
            // 加到消息队列
            addToMQ(jedis, uid, iids, type, 0);
        }
        return 0;
    }

    /**
     * 获取用户分页点
     * 
     * @param jedis
     * @param type
     * @param uid
     * @return
     */
    public Map<Long, Long> pageStoreMap(Jedis jedis, int type, long uid) {
        String key = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        Set<redis.clients.jedis.Tuple> tuples = jedis.zrevrangeWithScores(key,
                0, -1);
        Map<Long, Long> ret = Maps.newLinkedHashMap();

        for (redis.clients.jedis.Tuple tuple : tuples) {
            Long pid = Long.valueOf(tuple.getElement());
            long timestamp = Double.valueOf(tuple.getScore()).longValue();
            ret.put(pid, timestamp);
        }

        return ret;
    }

    /**
     * 获取用户收藏数
     * 
     * @param jedis
     * @param type
     * @param uid
     * @return
     */
    public int getUserCount(Jedis jedis, int type, long uid) {
        String key = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        return jedis.zcard(key).intValue();
    }

    /**
     * 获取iids中收藏的ids
     * 
     * @param jedis
     * @param type
     * @param uid
     * @param iids
     * @return
     */
    public List<Long> getBatchItemStatus(Jedis jedis, int type, long uid,
            List<Long> iids) {
        String userKey = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        List<Long> members = RedisUtils.zismemberL(jedis, userKey, iids);
        return members;
    }
    
    /**
     * 获取iid收藏个数，返回map值
     * iid -> count
     * @param jedis
     * @param type
     * @param uid
     * @param iids
     * @return
     */
    public Map<Long, Double> getBatchItemStoreTime(Jedis jedis, int type, Long uid, List<Long> iids) {
    	if(uid == null) {
    		return Maps.newHashMap();
    	}
        String userKey = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        Map<Long, Double> ret = RedisUtils.zbatchscoreL(jedis, userKey, iids);
        return ret;
    }

    /**
     * 获取item收藏数
     * 
     * @param jedis
     * @param type
     * @param iids
     * @return
     */
    public Map<Long, Integer> getBatchItemCount(Jedis jedis, int type,
            List<Long> iids) {
        String countKey = RedisKeyGenerator.generateStoreCounterKey(type);
        return RedisUtils.zbatchcountL(jedis, countKey, iids);
    }

    private boolean exists(Jedis jedis, int type, long uid, long iid) {
        String key = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        return RedisUtils.zexists(jedis, key, iid + "");
    }

    private void addUserMap(Jedis jedis, int type, long uid, List<Long> iids) {
        String key = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        Map<String, Double> map = Maps.newHashMap();
        double timestamp = System.currentTimeMillis();
        for (long iid : iids) {
            map.put(iid + "", timestamp++);
        }
        jedis.zadd(key, map);
    }

    private void delUserMap(Jedis jedis, int type, long uid, List<Long> iids) {
        String key = RedisKeyGenerator.generateStoreZSetKey(type, uid);
        List<String> members = ListUtils.convert(iids, String.class);
        jedis.zrem(key, members.toArray(new String[0]));
    }

    /**
     * add change list to mq
     * 
     * @param jedis
     * @param uid
     * @param iids
     * @param type
     * @param status
     */
    private void addToMQ(Jedis jedis, long uid, List<Long> iids, int type,
            int status) {
        BatchChangeItem changeItem = new BatchChangeItem(uid, status, type,
                iids);
        String key = RedisKeyGenerator.generateStoreMQKey();
        jedis.lpush(key, messageSerializer.serialize(changeItem));
    }

    /**
     * add store counters
     * 
     * @param jedis
     * @param type
     * @param iids
     */
    private void addCounter(Jedis jedis, int type, List<Long> iids) {
        changeCounter(jedis, type, iids, 1);
    }

    /**
     * del store counter
     * 
     * @param jedis
     * @param type
     * @param iids
     */
    private void delCounter(Jedis jedis, int type, List<Long> iids) {
        changeCounter(jedis, type, iids, -1);
    }

    /**
     * batch add store count by count
     * 
     * @param jedis
     * @param type
     * @param iids
     * @param count
     */
    private void changeCounter(Jedis jedis, int type, List<Long> iids, int count) {
        String key = RedisKeyGenerator.generateStoreCounterKey(type);
        List<String> members = ListUtils.convert(iids, String.class);
        RedisUtils.batchAddCounter(jedis, key, members, count);
    }
    
    /**
     * 从消息队列获取变更细节，回填数据库
     * @param jedis
     * @param batch
     * @return
     */
    public int dump(Jedis jedis, int batch) {
        String key = RedisKeyGenerator.generateStoreMQKey();
        List<String> messages = RedisUtils.lpopbatch(jedis, key, batch);
        int ret = messages.size();
        Map<Triple<Integer, Long, Long>, Integer> map = Maps.newHashMap();
        if (CollectionUtils.isEmpty(messages)) {
            return 0;
        }
        
        // 合并去重
        for (String message : messages) {
            BatchChangeItem item = messageSerializer.deserialize(message, BatchChangeItem.class);
            for (Long iid : item.getI()) {
                Triple<Integer, Long, Long> triple = new Triple<Integer, Long, Long>(
                    item.getT(), item.getU(), iid);
                map.put(triple, item.getS());
            }
        }
        
        // 分状态打散
        Multimap<Integer, Triple<Integer, Long, Long>> statusMap = MapUtils.convert(map);
        
        // 0 -> del -1,1 -> insert/update
        for (int status : statusMap.keySet()) {
            if (status == 0) {
                delFromDB(statusMap.get(status));
            } else {
                addToDB(status, statusMap.get(status));
            }
        }
        return ret;
    }
    
    private int addToDB(int status, Collection<Triple<Integer, Long, Long>> triples) {
        List<Store> stores = Lists.newArrayList();
        for (Triple<Integer, Long, Long> triple : triples) {
            stores.add(new Store(triple.getS(), triple.getT(), triple.getF()));
        }
        storeDAO.add(stores);
        return triples.size();
    }
    
    private int delFromDB(Collection<Triple<Integer, Long, Long>> triples) {
        if (CollectionUtils.isEmpty(triples)) {
            return 0;
        }
        Map<Integer, Multimap<Long, Long>> map = FlatUtils.tri2tup(triples);
        for (int type : map.keySet()) {
            storeDAO.del(type, map.get(type));
        }
        return triples.size();
    }
}
